﻿using System;
using System.Collections.Generic;
using System.Reflection;
using ProjectDanmaku.Core;
using ProjectDanmaku.Scenario;
using ProjectDanmaku.Util;
using UnityEngine;

namespace ProjectDanmaku.Avatar.Basic
{
    internal struct MethodBounding
    {
        public MethodInfo Method;
        public object Self;

        public MethodBounding(MethodInfo method, object self)
        {
            this.Method = method;
            this.Self = self;
        }

        public object Invoke(params object[] args)
        {
            return Method.Invoke(Self, args);
        }
    }
    
    public class AvatarController : MonoBehaviour
    {
        private GameObject currentAvatarGameObject;
        
        private readonly List<MethodBounding> _movementMethods = new List<MethodBounding>();
        private readonly List<MethodBounding> _enterMonsterMethods = new List<MethodBounding>();
        private readonly List<MethodBounding> _leaveMonsterMethods = new List<MethodBounding>();
        private readonly List<MethodBounding> _fireMethods = new List<MethodBounding>();

        public GameObject initAvatarPrefab;

        private void CollectMethodInComponents()
        {
            var comps = GetComponentsInChildren<AvatarBehavior>();

            foreach (var comp in comps)
            {
                CollectObjectMethods(_movementMethods, comp, "Movement", typeof(Vector2));
                CollectObjectMethods(_fireMethods, comp, "Fire");
                CollectObjectMethods(_enterMonsterMethods, comp, "OnEnterMonsterState");
                CollectObjectMethods(_leaveMonsterMethods, comp, "OnLeaveMonsterState");
            }
        }

        private void CollectObjectMethods(
            List<MethodBounding> methodList, object obj, string methodName, params Type[] paramTypes)
        {
            var type = obj.GetType();
            var method = ReflectionUtility.GetInstanceMethodByParamType(type, methodName, paramTypes);
            if (method != null)
                methodList.Add(new MethodBounding(method, obj));
        }

        private void InvokeMethodList(List<MethodBounding> methodList, params object[] args)
        {
            foreach (var method in methodList)
            {
                method.Invoke(args);
            }
        }

        private void CreateAvatar(GameObject avatarPrefab)
        {
            if (currentAvatarGameObject)
            {
                Destroy(currentAvatarGameObject);
            }

            currentAvatarGameObject = Instantiate(avatarPrefab, transform);
            currentAvatarGameObject.transform.position = CoordUtility.GetPosFromCoord(new Vector2(8, 3));
        }

        private bool CheckFire()
        {
            return Input.GetKey(KeyCode.Z) || Application.isMobilePlatform;
        }

        private Vector2 GetMovementFromKeyboard()
        {
            var x = 0;
            var y = 0;

            if (Input.GetKey(KeyCode.UpArrow))
            {
                y = 1;
            } else if (Input.GetKey(KeyCode.DownArrow))
            {
                y = -1;
            }

            if (Input.GetKey(KeyCode.RightArrow))
            {
                x = 1;
            } else if (Input.GetKey(KeyCode.LeftArrow))
            {
                x = -1;
            }

            return new Vector2(x, y).normalized;
        }

        private void Start()
        {
            if (initAvatarPrefab)
                CreateAvatar(initAvatarPrefab);
            CollectMethodInComponents();
        }

        private void Update()
        {
            if ((DanmakuScenario.DisabledUnitFlags & UnitTypeFlags.Player) > 0)
                return;
            
            Vector2 rawMovement = GetMovementFromKeyboard();
            Movement(rawMovement);

            if (CheckFire())
            {
                InvokeMethodList(_fireMethods);
            }

            // 不使用 LShift 是由于 LShift + Up + Left 的组合键在 Surface 上的 Type Cover 不可用 :/
            
            if (/* Input.GetKeyDown(KeyCode.LeftShift) */ Input.GetKeyDown(KeyCode.LeftControl))
            {
                InvokeMethodList(_enterMonsterMethods);
            }

            if (/* Input.GetKeyUp(KeyCode.LeftShift) || */ Input.GetKeyUp(KeyCode.LeftControl))
            {
                InvokeMethodList(_leaveMonsterMethods);
            }
        }

        public void Movement(Vector2 rawMovement)
        {
            InvokeMethodList(_movementMethods, rawMovement);
        }
    }
}